feat: expose evaluated scenario state on scenario binary_sensors - #171
feat: expose evaluated scenario state on scenario binary_sensors#171lollox80 wants to merge 6 commits into
Conversation
`expose_entities()` already creates `binary_sensor.supernotify_scenario_<name>` but with `state=STATE_UNKNOWN` hard-coded and never refreshed. - evaluate each scenario's conditions with neutral variables (current occupancy, PRIORITY_MEDIUM - the same basis as enquire_active_scenarios) and publish on/off - refresh every minute (time/date driven scenarios) and on state changes of the entities extracted from the conditions via `condition.async_extract_entities` - scenarios whose conditions reference no entity (priority-only, or triggered through applied_scenarios) stay `unknown`, which is the honest answer for them - new `HomeAssistantAPI.subscribe_interval()` helper so the timer is owned by hass_api and torn down with the other subscriptions - test double: expose `loop` on MockableHomeAssistant so timers can be registered in unit tests Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01HydbFBwYt3HV83xQ4UzdjV
for more information, see https://pre-commit.ci
for more information, see https://pre-commit.ci
|
I think this needs a config switch, globally, perhaps per scenario, since it has potential to increase compute a lot, including some folk running this on a tiny SBC I had thought that scenario should be broken out to a separate plugin, since the idea of switchable fragments of config could be more useful, but its a lot of work to pull apart, especially if no concrete second case (though maybe I'm overlooking my own AutoArm). Having scenarios be properly event driven, reactive scenarios, would be sort of thing that plugin would be good at |
Evaluating scenario conditions costs whatever the conditions cost, so: - a state change now re-evaluates only the scenarios that depend on the entity that changed, using the index already collected for the subscriptions, rather than the whole registry on every event - scenario_state.enabled: false subscribes to nothing and starts no timer - scenario_state.refresh_interval tunes the sweep, 0 drops it while keeping the reactive path - expose_state: false keeps an individual expensive scenario out of it Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KmMatiNzpXzZmrwWEtCT3x
for more information, see https://pre-commit.ci
|
Fair concern, and it turns out the numbers back it — but they point at the fan-out before the switch. I benchmarked the refresh on the PR branch (Python 3.13, desktop CPU, 20 runs averaged), evaluating every scenario in the registry the way
So the cost is driven by what the conditions do, not by how many scenarios there are: a 20x spread between simple conditions and the kind of template people actually write for "any window open" or a DND window. On an SBC that is a few hundred milliseconds, and that is per refresh. Which is the real problem: the refresh currently re-evaluates every scenario on every state change of any watched entity. One motion sensor flipping re-runs all 25. The mapping needed to avoid that is already built in this PR — That, and the switch you asked for, are now pushed onto this branch:
Seven new tests cover the dispatch and the switch, including that an unrelated entity changing evaluates nothing at all. Full suite 1047 passing, I put the global option in On the plugin idea: worth saying that the entity extraction in this PR is the same thing reactive scenarios would need — knowing which entities a scenario actually depends on is the prerequisite for waking it on an event instead of sweeping it on a timer. So even if scenarios do get pulled out later, that part isn't wasted work. If any of the naming or the defaults isn't what you had in mind, it is all in one commit and easy to reshape. |
Motivation
expose_entities() already creates binary_sensor.supernotify_scenario_ for every scenario, but the state is hard-coded to unknown and never refreshed — unlike the delivery/transport/recipient entities, which report on/off. Users building dashboards (or automations) around "which scenarios are active right now" currently have to poll supernotify.enquire_active_scenarios. This maps onto the "Fourth" phase of the config flow roadmap (scenarios: view / enable-disable) and is independent of the config flow itself.
What it does
Evaluates each scenario's conditions with neutral variables — current occupancy and PRIORITY_MEDIUM, the same basis as enquire_active_scenarios() — and publishes on/off.
Refreshes on a 1-minute timer (time/date driven scenarios, and any dependency not captured by entity extraction) and immediately on state changes of the entities referenced by the conditions, extracted with condition.async_extract_entities. Pure in-memory evaluation over cached states, no I/O.
Scenarios whose conditions reference no entity (priority-only, or triggered through applied_scenarios like a manual emergency) stay unknown: their state is undefined outside of a notification, and saying so is more honest than guessing.
Adds HomeAssistantAPI.subscribe_interval(seconds, callback) so the timer is owned by hass_api and torn down with the other subscriptions on disconnect.
Attributes are unchanged (enabled, alias, …); enabled stays an attribute rather than becoming the state.
Design note
From mapping my own 25 scenarios: 1 is a manual switch, 17 self-activate on priority/time/occupancy, 7 are driven by existing helpers. Exposing the evaluated state read-only (with helpers referenced as inputs) keeps that automation intact, whereas modelling "scenario = helper switch" would break most of them. Happy to discuss the semantics (evaluated vs enabled) if you see it differently.
Files
notify.py (+68), hass_api.py (new subscribe_interval), CHANGELOG.md, tests/components/supernotify/test_scenario_state.py (4 tests), hass_setup_lib.py / conftest.py (expose loop on the HA mock so timers can be registered in unit tests).
Testing
Unit: 4 new tests + full suite green. Real HA: (compilare: after deploy, binary_sensor.supernotify_scenario_morning/night/dnd_globale report on/off and flip on helper changes; emergency/critical_panic stay unknown).